package data;

import musicalgo.data.DataIO;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class DataPreProcessor extends DataNormalizer
{
    NormalizationStyle normalizationStyle;

    private int dataPointCount;

    private double percentOverlap = 0.1;

    private int moments = 1;

    private boolean addFeatures = false;


    private List<SequentialData> processedDataSet;
    private String path;
    private int dataSetSize;

    public DataPreProcessor(String path, int dataSetSize)
    {

        this.path = path;
        this.dataSetSize = dataSetSize;
    }
    
    public void changeData(String path, int dataSetSize)
    {
        this.path = path;
        this.dataSetSize = dataSetSize;
        loadNormalized(path, dataSetSize);
    }

    public List<SequentialData> processData()
    {
        if(rawData == null)
        {
            loadNormalized(path, dataSetSize);
        }
        processedDataSet = new ArrayList<SequentialData>();

        for (SequentialData sd : rawData)
        {
            if (addFeatures)
            {
                processedDataSet.add(getMergedAndExpandedData(dataPointCount, sd, percentOverlap, moments));
            }
            else
            {
                processedDataSet.add(getMergedData(dataPointCount, sd, percentOverlap));
            }
        }


        return processedDataSet;

    }


    /**
     * creates a reduced size SequetialData which merges dataPoints
     * by computing the average of a series of partially overlapping subsets
     * of the initial dataPoint set.
     *
     * @param numPoints      desired number of dataPoints
     * @param percentOverlap percentage of overlap for merged Sections
     * @return merged data
     */
    public SequentialData getMergedAndExpandedData(int numPoints, SequentialData data, double percentOverlap, int addedMoments)
    {
        int mindiv = data.getdataPoints().size() / numPoints;

        int overlap = (int) Math.ceil(percentOverlap * mindiv);

        ArrayList<double[]> mergedData = new ArrayList<double[]>(numPoints);

        for (int i = 0; i < numPoints; i++)
        {
            int start = Math.max(0, i * mindiv - overlap);
            int end = Math.min(data.getdataPoints().size(), (i + 1) * mindiv + overlap);
            mergedData.add(getAverageAndMoments(data.getdataPoints().subList(start, end), addedMoments));
        }

        return new SequentialData(mergedData);

    }


    /**
     * creates a reduced size SequetialData which merges dataPoints
     * by computing the average of a series of partially overlapping subsets
     * of the initial dataPoint set.
     *
     * @param numPoints      desired number of dataPoints
     * @param percentOverlap percentage of overlap for merged Sections
     * @return merged data
     */
    public SequentialData getMergedData(int numPoints, SequentialData data, double percentOverlap)
    {
        int mindiv = data.getdataPoints().size() / numPoints;

        int overlap = (int) Math.ceil(percentOverlap * mindiv);

        ArrayList<double[]> mergedData = new ArrayList<double[]>(numPoints);

        for (int i = 0; i < numPoints; i++)
        {
            int start = Math.max(0, i * mindiv - overlap);
            int end = Math.min(data.getdataPoints().size(), (i + 1) * mindiv + overlap);
            mergedData.add(getAverage(data.getdataPoints().subList(start, end)));
        }

        return new SequentialData(mergedData);

    }

    private double[] getAverage(List<double[]> dataPoints)
    {
        int dimensions = dataPoints.get(0).length;
        int numPoints = dataPoints.size();

        double[] s = new double[dimensions];
        for (double[] seg : dataPoints)
        {
            int k = 0;
            for (int i = 0; i < dimensions; i++)
            {
                s[k++] += seg[i];
            }
        }

        for (int i = 0; i < dimensions; i++)
        {
            s[i] /= numPoints;
        }

        return s;
    }

    private double[] getAverageAndMoments(List<double[]> dataPoints, int moments)
    {

        double[] average = getAverage(dataPoints);
        double[] result = Arrays.copyOf(average, average.length * (moments + 1));

        int k = average.length;
        for (int i = 0; i < moments; i++)
        {
            double[] moment = centralMoment(i + 2, dataPoints, average);
            for (double m : moment)
            {
                result[k++] = m;
            }
        }

        return result;

    }


    private void loadNormalized(String path, int dataSetSize)
    {
        setRawData(DataIO.loadSongs(path).subList(0, Math.min(2500, dataSetSize)));
        normalize(normalizationStyle);
    }


    public void setAddFeatures(boolean addFeatures)
    {
        this.addFeatures = addFeatures;
    }

    public void setDataPointCount(int dataPointCount)
    {
        this.dataPointCount = dataPointCount;
    }

    public void setPercentOverlap(double percentOverlap)
    {
        this.percentOverlap = percentOverlap;
    }

    public void setMoments(int moments)
    {
        this.moments = moments;
    }

    public void setNormalizationStyle(NormalizationStyle normalizationStyle)
    {
        this.normalizationStyle = normalizationStyle;
    }


    public List<SequentialData> getProcessedDataSet()
    {
        return processedDataSet;
    }


}
